package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/gogo/protobuf/types"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"inventory/api/bmember"
	"inventory/api/inventory"
	"inventory/internal/conf"
	"inventory/internal/constant"
	"inventory/internal/pkg/generator"
	"inventory/pkg/env"
	"inventory/pkg/orm"
	"inventory/pkg/util"
	"inventory/pkg/wechat"
	"strconv"
	"time"

	"inventory/internal/model"
	"inventory/pkg/ecode"
)

// CreateCombinedProcessOrder 创建组合单
func (s *Service) CreateCombinedProcessOrder(ctx context.Context, req *inventory.CreateCombinedProcessOrderRequest) (reply *inventory.BatchCreateOrderReply, err error) {
	var orderNumber string
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		now := time.Now()
		result := make([]*inventory.CreateOrderReply, len(req.OrderList))
		var (
			calculationRequest *model.ProcessRequest
			calculationBody    *model.CalculationBody
			freezeOrder        *model.FreezeOrder
			freezeDetails      []*model.FreezeDetail
			processOrders      []*model.ProcessOrder
			freezeValidCount   int
		)
		if freezeOrder, err = s.dao.GetFreezeOrder(ctx, req.FreezeOrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrOrderNotExist
			}
			return
		}
		if inventory.SourceEnum(freezeOrder.Source) == inventory.SourceEnum_MALL_ORDER {
			if calculationRequest, err = s.dao.LockCalculatedProcessRequest(
				ctx, freezeOrder.RequestNum, freezeOrder.Source); err != nil {
				if errors.Cause(err) == gorm.ErrRecordNotFound {
					err = constant.ErrCalculationNotExist
				}
				return
			}
			if calculationRequest.Status != constant.CalculateRequestFrozen {
				if calculationRequest.Status == constant.CalculateRequestFINISHED {
					// 已成功履约，需要组装幂等成功请求
					if processOrders, err = s.dao.FindProcessOrdersByFreezeOrderID(ctx, freezeOrder.ID); err != nil {
						return
					}
					for i, order := range processOrders {
						result[i] = &inventory.CreateOrderReply{
							OrderNumber: order.OrderNumber, ExtOrderNumber: order.ExtOrderNumber,
							Status: uint32(order.Status),
						}
					}
					reply = &inventory.BatchCreateOrderReply{Result: result}
					return
				}
				err = constant.ErrCalculationNotExist
				return
			}
			if err = json.Unmarshal(calculationRequest.Body, &calculationBody); err != nil {
				return
			}
			if len(calculationBody.Packages) != len(req.OrderList) {
				err = ecode.AppendMessage(constant.ErrCalculationMismatch, "计算包裹个数:%d, 下单包裹个数:%d",
					len(req.OrderList), len(calculationBody.Packages))
				return
			}
		}
		// 地址转json
		var addressInfo []byte
		if req.AddressInfo != nil {
			if calculationBody != nil && (req.AddressInfo.Lat != calculationBody.Lat || req.AddressInfo.Lng != calculationBody.Lng || req.AddressInfo.CityCode != calculationBody.CityCode) {
				calAddress, _ := json.Marshal(req.AddressInfo)
				reqAddress, _ := json.Marshal(req.AddressInfo)
				err = ecode.AppendMessage(constant.ErrCalculationMismatch, "计算地址信息:%s, 下单地址信息: %s",
					calAddress, reqAddress)
				return
			}
			if addressInfo, err = json.Marshal(req.AddressInfo); err != nil {
				return
			}
		}
		if freezeOrder.Status != constant.FreezeOrderInit {
			err = constant.ErrInvalidFreezeOrder
			return
		}
		type warehouseStorageKey struct {
			warehouseID uint32
			storageKey  string
		}
		storageKeyMap := make(map[warehouseStorageKey]float64)
		expectDateMap := make(map[int]util.NullTime)
		for index, orderInfo := range req.OrderList {
			var calculationPackage *inventory.Package
			if calculationBody != nil {
				calculationPackage = calculationBody.Packages[index]
				if orderInfo.DeliveryType != calculationPackage.DeliverType || orderInfo.WarehouseId != calculationPackage.Id || orderInfo.DeliveryFee != calculationPackage.ExpectFee {
					err = ecode.AppendMessage(constant.ErrCalculationMismatch, "计算包裹信息:%d,%d,%d 下单包裹信息: %d,%d,%d",
						calculationPackage.DeliverType, calculationPackage.Id, calculationPackage.ExpectFee,
						orderInfo.DeliveryType, orderInfo.WarehouseId, orderInfo.DeliveryFee)
					return
				}
			}
			var totalAmount uint64 = 0
			if len(orderInfo.Details) == 0 {
				err = ecode.BadRequest("组合下单第%d单明细数量为0", index)
				return
			}
			for _, detail := range orderInfo.Details {
				key := warehouseStorageKey{
					warehouseID: orderInfo.WarehouseId,
					storageKey:  detail.StorageKey,
				}
				if _, ok := storageKeyMap[key]; !ok {
					storageKeyMap[key] = 0
				}
				storageKeyMap[key] += float64(detail.Quantity)
				totalAmount += detail.Origin
			}
			if calculationPackage != nil && totalAmount != calculationPackage.TotalAmount {
				err = ecode.AppendMessage(constant.ErrCalculationMismatch, "计算包裹商品总价:%d 下单包裹商品总价: %d",
					calculationPackage.TotalAmount, totalAmount)
				return
			}
			deliverType := orderInfo.DeliveryType
			if deliverType != inventory.DeliverType_PICK_UP && deliverType != inventory.DeliverType_SKIP && req.AddressInfo == nil {
				err = constant.ErrAddressMissing
				return
			}
			expectDate := util.NullTime{}
			if orderInfo.ExpectDeliverDate != "" {
				var expectRawDate time.Time
				if expectRawDate, err = util.ParseTime(orderInfo.ExpectDeliverDate); err != nil {
					err = ecode.BadRequest("ExpectDeliverDate:" + orderInfo.ExpectDeliverDate)
					return
				}
				_ = expectDate.Scan(expectRawDate)
			}
			expectDateMap[index] = expectDate
		}
		if freezeDetails, err = s.dao.FindFreezeOrderNonZeroDetails(ctx, freezeOrder.ID); err != nil {
			return
		}
		detailMap := make(map[uint64]*model.FreezeDetail)
		bomMap := make(map[string]map[string]float64)
		var materialIds []uint64
		materialRemarkMap := make(map[string]json.RawMessage)
		for _, freezeDetail := range freezeDetails {
			detailMap[freezeDetail.ID] = freezeDetail
			if inventory.StorageType(freezeDetail.StorageType) != inventory.StorageType_PROD {
				parentDetail := detailMap[freezeDetail.ParentID]
				if _, ok := bomMap[parentDetail.StorageKey]; !ok {
					bomMap[parentDetail.StorageKey] = make(map[string]float64)
				}
				bomMap[parentDetail.StorageKey][freezeDetail.StorageKey] = freezeDetail.TargetQuantity / parentDetail.TargetQuantity
				var materialID int
				if materialID, err = strconv.Atoi(freezeDetail.StorageKey[2:]); err != nil {
					return
				}
				materialIds = append(materialIds, uint64(materialID))
				continue
			}
			requireQuantity, ok := storageKeyMap[warehouseStorageKey{
				warehouseID: freezeDetail.WarehouseID,
				storageKey:  freezeDetail.StorageKey,
			}]
			if !ok || requireQuantity != freezeDetail.ValidQuantity {
				// 基于冻结单明细关键字找不到下单明细 || 数量与冻结中数量不匹配
				err = constant.ErrMisMatchFreezeOrder
				return
			}
			freezeValidCount += 1
		}
		if len(storageKeyMap) != freezeValidCount {
			err = constant.ErrMisMatchFreezeOrder
			return
		}
		if len(bomMap) != 0 {
			materialRemarkMap, err = s.dao.BuildProcessMaterialRemarks(ctx, materialIds)
			if err != nil {
				return
			}
		}
		var (
			orderNumberList []string
			uniqueKey       string
		)
		if req.MemberId != "" || inventory.SourceEnum(freezeOrder.Source) == inventory.SourceEnum_MALL_ORDER {
			uniqueKey = req.MemberId
		} else {
			uniqueKey = req.OrderKey
		}
		if orderNumberList, err = generator.GenerateOrderNumberList(
			now, constant.OrderTypeProcess, uniqueKey, len(req.OrderList)); err != nil {
			return
		}
		for index, orderInfo := range req.OrderList {
			orderNumber = orderNumberList[index]
			var (
				orderAddressInfo []byte = nil
				orderStatus             = inventory.ProcessOrderStatus_ProcessOrderWaitStock
				orderRemark      []byte
				prodCount        = 0
			)
			deliverType := orderInfo.DeliveryType
			if deliverType != inventory.DeliverType_PICK_UP && deliverType != inventory.DeliverType_SKIP {
				orderAddressInfo = addressInfo
				orderStatus = inventory.ProcessOrderStatus_ProcessOrderWaitShip
			}
			if orderInfo.Remark != nil {
				if orderRemark, err = json.Marshal(orderInfo.Remark); err != nil {
					return
				}
			} else {
				orderRemark = []byte("{}")
			}
			for _, d := range orderInfo.Details {
				prodCount += int(d.Quantity)
			}
			processOrder := model.ProcessOrder{
				OrderNumber:    orderNumber,
				ExtOrderNumber: orderInfo.ExtOrderNumber,
				FreezeOrderID: sql.NullInt64{
					Int64: int64(freezeOrder.ID),
					Valid: true,
				},
				Direction:         constant.DirectionIn,
				Source:            freezeOrder.Source,
				WarehouseID:       orderInfo.WarehouseId,
				ProdTrans:         int64(orderInfo.ProdTrans),
				ExpectDeliverFee:  int64(orderInfo.DeliveryFee),
				AddressInfo:       orderAddressInfo,
				MemberID:          req.MemberId,
				DeliverType:       uint8(orderInfo.DeliveryType),
				SubmitTime:        now,
				Status:            uint8(orderStatus),
				Remark:            orm.JSON(orderRemark),
				ExpectDeliverDate: expectDateMap[index],
				ProdCount:         uint32(prodCount),
				UpdateTime:        now,
			}
			if err = s.dao.Create(ctx, &processOrder); err != nil {
				return
			}
			for _, d := range orderInfo.Details {
				var detailRemark []byte
				// 商品编码 = 仓储对象key
				if detailRemark, err = json.Marshal(&model.Remark{
					Name:    d.Remark.Name,
					SkuName: d.Remark.SkuName,
					Unit:    d.Remark.Unit,
					Code:    d.StorageKey,
				}); err != nil {
					return
				}
				detail := model.ProcessDetail{
					ID:          0,
					OrderID:     processOrder.ID,
					StorageType: uint8(inventory.StorageType_PROD),
					StorageKey:  d.StorageKey,
					ParentID:    0,
					WarehouseID: orderInfo.WarehouseId,
					Price:       d.Price,
					Quantity:    float64(d.Quantity),
					Status:      constant.ProcessDetailInit,
					NotifyFlag:  false,
					Remark:      detailRemark,
					CreateTime:  now,
					UpdateTime:  now,
					Origin:      int64(d.Origin),
					Discount:    int64(d.Discount),
					Trans:       int64(d.Trans),
				}
				if bb, ok := bomMap[d.StorageKey]; ok {
					if err = s.dao.Create(ctx, &detail); err != nil {
						return
					}
					for storageKey, quantity := range bb {
						if err = s.dao.Create(ctx, &model.ProcessDetail{
							OrderID:      processOrder.ID,
							StorageType:  uint8(inventory.StorageType_MATERIAL),
							StorageKey:   storageKey,
							ParentID:     detail.ID,
							WarehouseID:  orderInfo.WarehouseId,
							Price:        0,
							Quantity:     quantity * float64(d.Quantity),
							Status:       constant.ProcessDetailInit,
							NotifyFlag:   false,
							Remark:       orm.JSON(materialRemarkMap[storageKey]),
							CreateTime:   now,
							UpdateTime:   now,
							Origin:       0,
							Discount:     0,
							Trans:        0,
							IsAccounting: true,
						}); err != nil {
							return
						}
					}
				} else {
					detail.IsAccounting = true
					if err = s.dao.Create(ctx, &detail); err != nil {
						return
					}
				}
			}
			statsMsg, _ := json.Marshal(&inventory.InventoryOrderUpdatePayload{
				OrderNumber:    processOrder.OrderNumber,
				ExtOrderNumber: processOrder.ExtOrderNumber,
				Source:         inventory.SourceEnum(processOrder.Source),
				Timestamp:      now.Unix(),
				Status:         inventory.ProcessOrderStatus(processOrder.Status),
				DeliverType:    inventory.DeliverType(processOrder.DeliverType),
				DeliverMemo:    &inventory.InventoryOrderUpdatePayload_DeliverMemo{},
			})
			if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicInventoryStatus, statsMsg); err != nil {
				return err
			}
			result[index] = &inventory.CreateOrderReply{
				OrderNumber: orderNumber, ExtOrderNumber: orderInfo.ExtOrderNumber,
				Status: uint32(processOrder.Status)}
		}
		if calculationRequest != nil {
			if err = s.dao.Update(ctx, &calculationRequest, map[string]interface{}{
				"status":      constant.CalculateRequestFINISHED,
				"update_time": time.Now(),
			}); err != nil {
				return
			}
		}
		if err = s.markFreezeOrderConsumed(ctx, freezeOrder, now); err != nil {
			return
		}
		reply = &inventory.BatchCreateOrderReply{Result: result}
		return
	})
	if err != nil && conf.Conf.Env != env.Local {
		wechat.AsyncSendMarkDown(
			ctx, wechat.FormatBugMessage(
				ctx, conf.Conf.Env, conf.Conf.ServiceName, "CreateCombinedProcessOrder",
				"履约单创建失败",
				fmt.Sprintf("error: %+v", err)))
	}
	return
}

// CancelProcessOrder 取消履约单
func (s *Service) CancelProcessOrder(ctx context.Context, req *inventory.CancelProcessOrderRequest) (reply *inventory.DefaultResp, err error) {
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		processOrder := model.ProcessOrder{}
		if req.OrderNumber != "" {
			err = s.dao.Lock(ctx, model.ProcessOrder{
				OrderNumber: req.OrderNumber,
			}, &processOrder)
		} else {
			err = s.dao.Lock(ctx, model.ProcessOrder{
				Source:         uint8(req.Source),
				ExtOrderNumber: req.ExtOrderNumber,
			}, &processOrder)
		}
		if err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrOrderNotExist
			}
			return
		}
		now := time.Now()
		switch inventory.ProcessOrderStatus(processOrder.Status) {
		case inventory.ProcessOrderStatus_ProcessOrderWaitStock, inventory.ProcessOrderStatus_ProcessOrderWaitMentioned,
			inventory.ProcessOrderStatus_ProcessOrderWaitShip:
			// 履约单关联冻结单时，关闭冻结单
			freezeOrder := &model.FreezeOrder{ID: uint64(processOrder.FreezeOrderID.Int64)}
			if err = s.dao.Lock(ctx, freezeOrder, &freezeOrder); err != nil {
				if errors.Cause(err) == gorm.ErrRecordNotFound {
					err = constant.ErrOrderNotExist
				}
			}
			if freezeOrder.Status != constant.FreezeOrderProcessing {
				err = constant.ErrInvalidFreezeOrder
				return
			}
			var details []*model.ProcessDetail
			if details, err = s.dao.FindProcessOrderDetails(ctx, processOrder.ID); err != nil {
				return
			}
			unfreezeMap := make(map[string]float64)
			for _, d := range details {
				unfreezeMap[d.StorageKey] = d.Quantity
			}
			if err = updateFreezeOrder(ctx, s.dao, freezeOrder, &processOrder, unfreezeMap, now, false); err != nil {
				return
			}
			// 结束履约单
			remark := inventory.Remark{}
			if processOrder.Remark != nil {
				if err = json.Unmarshal(processOrder.Remark, &remark); err != nil {
					return
				}
			}
			remark.Reason = req.Reason
			var remarkJSON []byte
			if remarkJSON, err = json.Marshal(remark); err != nil {
				return
			}
			if err = s.dao.Update(ctx, &processOrder, map[string]interface{}{
				"update_time": now,
				"status":      int(inventory.ProcessOrderStatus_ProcessOrderCancelled),
				"refund_flag": 0,
				"remark":      remarkJSON,
			}); err != nil {
				return
			}
			if err = s.dao.TransContext(ctx).Model(&model.ProcessDetail{}).Where(
				"order_id = ?", processOrder.ID).Updates(
				map[string]interface{}{
					"update_time": now,
					"status":      int(inventory.ProcessOrderStatus_ProcessOrderCancelled),
				}).Error; err != nil {
				return
			}
			// 状态同步
			statsMsg, _ := json.Marshal(&inventory.InventoryOrderUpdatePayload{
				OrderNumber:    processOrder.OrderNumber,
				ExtOrderNumber: processOrder.ExtOrderNumber,
				Source:         inventory.SourceEnum(processOrder.Source),
				Timestamp:      now.Unix(),
				Status:         inventory.ProcessOrderStatus(processOrder.Status),
				DeliverType:    inventory.DeliverType(processOrder.DeliverType),
				DeliverMemo:    &inventory.InventoryOrderUpdatePayload_DeliverMemo{},
			})
			if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicInventoryStatus, statsMsg); err != nil {
				return err
			}
			return
		}
		return
	})
	if err != nil {
		return
	}
	reply = &inventory.DefaultResp{
		Status: true,
	}
	return
}

// GetProcessingProcessOrders 获取待处理履约单列表
func (s *Service) GetProcessingProcessOrders(ctx context.Context, req *inventory.GetProcessingOrdersRequest) (resp *inventory.GetProcessOrdersResponse, err error) {
	var endDate, startDate time.Time
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if startDate, endDate, err = verifyStartDateAndEndDate(req.StartDate, req.EndDate); err != nil {
		return
	}
	expectStartDate := util.ParseTimeWithZero(req.ExpectStartDate)
	expectEndDate := util.ParseTimeWithZero(req.ExpectStartDate)
	deliverStartDate := util.ParseTimeWithZero(req.DeliverStartDate)
	deliverEndDate := util.ParseTimeWithZero(req.DeliverEndDate)
	if !expectStartDate.IsZero() && !expectEndDate.IsZero() && expectEndDate.Sub(expectStartDate).Hours()/24 > 7 {
		err = ecode.BadRequest("预约时间跨度不超过1周")
		return
	}
	if !deliverStartDate.IsZero() && !deliverEndDate.IsZero() && deliverEndDate.Sub(deliverStartDate).Hours()/24 > 7 {
		err = ecode.BadRequest("发货时间跨度不超过1周")
		return
	}
	var (
		warehouse     *model.Warehouse
		total         int64
		processOrders []*model.ProcessOrder
		status        []int
	)
	if warehouse, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.DeliveryManagement); err != nil {
		return
	}
	if req.QueryStatus != 0 {
		status = []int{int(req.QueryStatus)}
	} else {
		status = []int{
			int(inventory.ProcessOrderStatus_ProcessOrderWaitStock),
			int(inventory.ProcessOrderStatus_ProcessOrderWaitMentioned),
			int(inventory.ProcessOrderStatus_ProcessOrderWaitShip)}
	}
	memberMap := make(model.MemberMap)
	if req.QueryMemberType != inventory.QueryMemberType_QUERY_MEMBER_TYPE_ALL && req.QueryMemberValue != "" {
		var members []*bmember.MemberMobileNickname
		if members, err = s.downstream.CallMemberMatch(ctx, sgId, req.QueryMemberType, req.QueryMemberValue); err != nil {
			return
		}
		if len(members) == 0 {
			resp = &inventory.GetProcessOrdersResponse{Orders: []*inventory.ProcessOrderView{}, Total: uint32(total)}
			return
		}
		memberMap.Init(members)
	}
	if total, processOrders, err = s.dao.FindProcessOrders(ctx, int(req.Id), status, req.QuerySource,
		memberMap.GetKeys(), req.QueryExtractKey, startDate, endDate, expectStartDate, expectEndDate,
		deliverStartDate, deliverEndDate, int(req.PageNo), int(req.PageSize), "", req.QueryExtOrderNumber,
		constant.DirectionIn); err != nil {
		return
	}
	var orderIDs []uint64
	for _, o := range processOrders {
		orderIDs = append(orderIDs, o.ID)
	}
	var processDetails []*model.ProcessDetail
	if processDetails, err = s.dao.FindProcessOrderDetailsByOrderIDs(ctx, orderIDs); err != nil {
		return
	}
	orderDetailsRef := make(map[uint64]map[string]float64)
	storageKeyMap := make(util.StringBoolMap)
	for _, d := range processDetails {
		if d.IsAccounting {
			if _, ok := orderDetailsRef[d.OrderID]; !ok {
				orderDetailsRef[d.OrderID] = make(map[string]float64)
			}
			orderDetailsRef[d.OrderID][d.StorageKey] += d.Quantity
			storageKeyMap[d.StorageKey] = true
		}
	}
	var storages []*model.Storage
	if storages, err = s.dao.FindWarehouseStoragesByStorageKeys(ctx, warehouse.ID, storageKeyMap.GetKeys()); err != nil {
		return
	}
	storageMap := make(model.StorageMap)
	storageMap.InitStorageMap(storages)
	orderStatusMap := make(map[string]int)
	for _, o := range processOrders {
		var storageStatus int // 库存状态，0~充足，1~不足
		for k, q := range orderDetailsRef[o.ID] {
			storage, ok := storageMap[k]
			// 不存在storageKey
			if !ok {
				// 强管控展示缺货
				if warehouse.StorageMode == constant.WarehouseStorageModeStrong {
					storageStatus = 1
					break
				}
				// 弱管控在无明确库存记录时视为无限量
				continue
			} else if storage.Quantity-q < 0 {
				// 有明确库存记录时，不区分管控模式，按照实际展示缺货标识
				storageStatus = 1
				break
			}
		}
		orderStatusMap[o.OrderNumber] = storageStatus
	}
	if len(memberMap) == 0 && len(processOrders) != 0 {
		queryMemberIDs := make(util.StringBoolMap)
		for _, o := range processOrders {
			queryMemberIDs[o.MemberID] = true
		}
		var members []*bmember.MemberMobileNickname
		if members, err = s.downstream.CallMemberQuery(ctx, queryMemberIDs.GetKeys()); err != nil {
			return
		}
		memberMap.Init(members)
	}

	views := make([]*inventory.ProcessOrderView, len(processOrders))
	for i, o := range processOrders {
		var (
			contact, address, deliverTime, expectDate string
		)
		if o.AddressInfo != nil {
			addressModel := &inventory.AddressInfo{}
			if err = json.Unmarshal(o.AddressInfo, addressModel); err != nil {
				return
			}
			address = fmt.Sprintf("%v/%v/%v", addressModel.Contact, addressModel.Mobile, addressModel.Address)
		}
		if member, ok := memberMap[o.MemberID]; ok {
			contact = fmt.Sprintf("%s/%s", member.Nickname, member.Mobile)
		}
		if o.DeliverTime.Valid {
			deliverTime = o.DeliverTime.Time.Format(util.DefaultLayout)
		}
		if o.ExpectDeliverDate.Valid {
			expectDate = o.ExpectDeliverDate.Time.Format(util.DateLayout)
		}
		views[i] = &inventory.ProcessOrderView{
			OrderNumber:       o.OrderNumber,
			ExtOrderNumber:    o.ExtOrderNumber,
			Source:            inventory.SourceEnum(o.Source),
			Contact:           contact,
			Address:           address,
			SubmitTime:        o.SubmitTime.Format(util.DefaultLayout),
			ProdCount:         o.ProdCount,
			Status:            inventory.ProcessOrderStatus(o.Status),
			StorageStatus:     uint32(orderStatusMap[o.OrderNumber]),
			Total:             uint64(o.ProdTrans + o.ExpectDeliverFee),
			ExpectDeliverDate: expectDate,
			DeliverTime:       deliverTime,
		}
	}
	resp = &inventory.GetProcessOrdersResponse{Orders: views, Total: uint32(total)}
	return
}

// GetFinishedProcessOrders 获取已完成履约单列表
func (s *Service) GetFinishedProcessOrders(ctx context.Context, req *inventory.GetFinishedOrdersRequest) (resp *inventory.GetProcessOrdersResponse, err error) {
	var endDate, startDate time.Time
	empId, sgId := req.WebToken.EmpId, req.WebToken.SgId
	if startDate, endDate, err = verifyStartDateAndEndDate(req.StartDate, req.EndDate); err != nil {
		return
	}
	deliverStartDate := util.ParseTimeWithZero(req.DeliverStartDate)
	deliverEndDate := util.ParseTimeWithZero(req.DeliverEndDate)
	if !deliverStartDate.IsZero() && !deliverEndDate.IsZero() && deliverEndDate.Sub(deliverStartDate).Hours()/24 > 7 {
		err = ecode.BadRequest("发货时间跨度不超过1周")
		return
	}
	if _, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.DeliveryManagement); err != nil {
		return
	}
	var (
		status        []int
		total         int64
		processOrders []*model.ProcessOrder
		orderIDs      []uint64
	)
	if req.QueryStatus != 0 {
		status = []int{int(req.QueryStatus)}
	} else {
		status = []int{
			int(inventory.ProcessOrderStatus_ProcessOrderShipped),
			int(inventory.ProcessOrderStatus_ProcessOrderReceived),
			int(inventory.ProcessOrderStatus_ProcessOrderCancelled)}
	}
	memberMap := make(model.MemberMap)
	if req.QueryMemberType != inventory.QueryMemberType_QUERY_MEMBER_TYPE_ALL && req.QueryMemberValue != "" {
		var members []*bmember.MemberMobileNickname
		if members, err = s.downstream.CallMemberMatch(ctx, sgId, req.QueryMemberType, req.QueryMemberValue); err != nil {
			return
		}
		if len(members) == 0 {
			resp = &inventory.GetProcessOrdersResponse{Orders: []*inventory.ProcessOrderView{}, Total: uint32(total)}
			return
		}
		memberMap.Init(members)
	}
	if total, processOrders, err = s.dao.FindProcessOrders(ctx, int(req.Id), status, req.QuerySource,
		memberMap.GetKeys(), req.QueryExtractKey, startDate, endDate, time.Time{}, time.Time{},
		deliverStartDate, deliverEndDate, int(req.PageNo), int(req.PageSize), "", req.QueryExtOrderNumber,
		constant.DirectionIn); err != nil {
		return
	}
	queryMemberIDs := make(util.StringBoolMap)
	for _, o := range processOrders {
		orderIDs = append(orderIDs, o.ID)
		queryMemberIDs[o.MemberID] = true
	}
	if len(memberMap) == 0 && len(processOrders) != 0 {
		var members []*bmember.MemberMobileNickname
		if members, err = s.downstream.CallMemberQuery(ctx, queryMemberIDs.GetKeys()); err != nil {
			return
		}
		memberMap.Init(members)
	}
	views := make([]*inventory.ProcessOrderView, len(processOrders))
	for i, o := range processOrders {
		var (
			contact, address, deliverTime, expectDate string
		)
		if o.AddressInfo != nil {
			addressModel := &inventory.AddressInfo{}
			if err = json.Unmarshal(o.AddressInfo, addressModel); err != nil {
				return
			}
			address = fmt.Sprintf("%v/%v/%v", addressModel.Contact, addressModel.Mobile, addressModel.Address)
		}
		if member, ok := memberMap[o.MemberID]; ok {
			contact = fmt.Sprintf("%s/%s", member.Nickname, member.Mobile)
		}
		if o.DeliverTime.Valid {
			deliverTime = o.DeliverTime.Time.Format(util.DefaultLayout)
		}
		if o.ExpectDeliverDate.Valid {
			expectDate = o.ExpectDeliverDate.Time.Format(util.DateLayout)
		}
		views[i] = &inventory.ProcessOrderView{
			OrderNumber:       o.OrderNumber,
			ExtOrderNumber:    o.ExtOrderNumber,
			Source:            inventory.SourceEnum(o.Source),
			Contact:           contact,
			Address:           address,
			SubmitTime:        o.SubmitTime.Format(util.DefaultLayout),
			ProdCount:         o.ProdCount,
			Status:            inventory.ProcessOrderStatus(o.Status),
			Total:             uint64(o.ProdTrans + o.ExpectDeliverFee),
			ExpectDeliverDate: expectDate,
			DeliverTime:       deliverTime,
		}
	}
	resp = &inventory.GetProcessOrdersResponse{Orders: views, Total: uint32(total)}
	return
}

// GetProcessOrderInfo 查看履约单详情
func (s *Service) GetProcessOrderInfo(ctx context.Context, req *inventory.CommonOrderRequest) (resp *inventory.GetOrderInfoResponse, err error) {
	var (
		warehouse      *model.Warehouse
		processOrder   *model.ProcessOrder
		processDetails []*model.ProcessDetail
		expressOrder   *model.ExpressOrder
		platform       *model.ExpressPlatform
		members        []*bmember.MemberMobileNickname
		storages       []*model.Storage
		mainRef        []*model.ProcessDetail
		deliveryStatus string
		contact        string
	)
	empId := req.WebToken.EmpId
	if warehouse, err = s.CheckUsagePermission(ctx, empId, req.PStoreId, req.Id, constant.DeliveryManagement); err != nil {
		return
	}
	if processOrder, err = s.dao.GetProcessOrder(ctx, req.OrderNumber); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrOrderNotExist
		}
		return
	}
	if processDetails, err = s.dao.FindProcessOrderDetails(ctx, processOrder.ID); err != nil {
		return
	}
	if processOrder.ExpressOrderID.Valid {
		if expressOrder, err = s.dao.GetExpressOrderByID(ctx, uint64(processOrder.ExpressOrderID.Int64)); err != nil {
			return
		}
		if platform, err = s.dao.GetPlatform(ctx, expressOrder.PlatformID); err != nil {
			return
		}
	} else {
		expressOrder = &model.ExpressOrder{}
		platform = &model.ExpressPlatform{Name: ""}
	}
	if members, err = s.downstream.CallMemberQuery(ctx, []string{processOrder.MemberID}); err != nil {
		return
	}
	if len(members) == 1 && strconv.Itoa(int(members[0].MemberId)) == processOrder.MemberID {
		contact = fmt.Sprintf("%s/%s", members[0].Nickname, members[0].Mobile)
	}
	storageKeyMap := make(util.StringBoolMap)
	subRef := make(map[uint64][]*model.ProcessDetail)
	for _, d := range processDetails {
		if d.IsAccounting {
			storageKeyMap[d.StorageKey] = true
		}
		if d.ParentID == 0 {
			mainRef = append(mainRef, d)
			subRef[d.ID] = []*model.ProcessDetail{}
		} else {
			subRef[d.ParentID] = append(subRef[d.ParentID], d)
		}
	}
	storageMap := make(model.StorageMap)
	if !processOrder.DeliverTime.Valid {
		// 未发货时需要加载库存信息
		if storages, err = s.dao.FindWarehouseStoragesByStorageKeys(ctx, warehouse.ID, storageKeyMap.GetKeys()); err != nil {
			return
		}
		storageMap.InitStorageMap(storages)
	}
	views := make([]*inventory.ProcessOrderDetailView, len(mainRef))
	for i, d := range mainRef {
		var (
			storageQuantity sql.NullFloat64
			subViews        []*inventory.ProcessOrderDetailView
		)
		if subs, hasSub := subRef[d.ID]; !hasSub || len(subs) == 0 {
			// 无子明细
			// 这里履约单如果创建成功 = 冻结成功 = 即强管控模式时必定有storage记录，无需判定模式；无记录一定为弱管控，可以视为无限量
			if storage, hasStorage := storageMap[d.StorageKey]; hasStorage {
				// 无需处理冻结库存，这里都是冻结过的
				storageQuantity = sql.NullFloat64{Float64: storage.Quantity, Valid: true}
			}
		} else {
			for _, sub := range subs {
				var subQuantity sql.NullFloat64
				if storage, hasStorage := storageMap[sub.StorageKey]; hasStorage {
					// 无需处理冻结库存，这里都是冻结过的
					_ = subQuantity.Scan(storage.Quantity)
					validQuantity := storage.Quantity * d.Quantity / sub.Quantity
					if !storageQuantity.Valid || storageQuantity.Float64 > validQuantity {
						// 未初始化 || 子物料相对库存小于对象库存时，更新为较小的
						storageQuantity = sql.NullFloat64{Float64: validQuantity, Valid: true}
					}
				} else if warehouse.StorageMode == constant.WarehouseStorageModeStrong {
					storageQuantity = sql.NullFloat64{Valid: true}
				}
				r := model.SnapShotRemark{}
				if err = json.Unmarshal(sub.Remark, &r); err != nil {
					return
				}
				subView := &inventory.ProcessOrderDetailView{
					StorageKey: sub.StorageKey,
					Price:      sub.Price,
					Quantity:   sub.Quantity,
					Name:       r.Name,
					Code:       r.Code,
					TypeName:   r.Type,
					Unit:       r.Unit,
					Amount:     sub.Trans,
					Status:     inventory.ProcessOrderStatus(processOrder.Status),
				}
				if subQuantity.Valid || warehouse.StorageMode == constant.WarehouseStorageModeStrong {
					subView.StorageQuantity = &types.DoubleValue{
						Value: subQuantity.Float64,
					}
				}
				subViews = append(subViews, subView)
			}
		}
		r := model.SnapShotRemark{}
		if err = json.Unmarshal(d.Remark, &r); err != nil {
			return
		}
		views[i] = &inventory.ProcessOrderDetailView{
			StorageKey: d.StorageKey,
			Price:      d.Price,
			Quantity:   d.Quantity,
			Name:       r.Name,
			SkuName:    r.SkuName,
			Code:       r.Code,
			TypeName:   r.Type,
			Unit:       r.Unit,
			Amount:     d.Trans,
			Status:     inventory.ProcessOrderStatus(processOrder.Status),
			Subs:       subViews,
		}
		if storageQuantity.Valid || warehouse.StorageMode == constant.WarehouseStorageModeStrong {
			views[i].StorageQuantity = &types.DoubleValue{
				Value: storageQuantity.Float64,
			}
		}
	}
	if inventory.DeliverType(platform.Mode) == inventory.DeliverType_CITY {
		deliveryStatus = expressOrder.DeliveryStatus()
	} else {
		deliveryStatus = fmt.Sprintf("%s/%s", platform.Name, expressOrder.TradeNo)
	}
	address := &inventory.AddressInfo{}
	if processOrder.AddressInfo != nil {
		if err = json.Unmarshal(processOrder.AddressInfo, address); err != nil {
			return
		}
	}

	var deliverTime, expectDate, prepareTime, completeTime string
	if processOrder.DeliverTime.Valid {
		deliverTime = processOrder.DeliverTime.Time.Format(util.DefaultLayout)
	}
	if processOrder.ExpectDeliverDate.Valid {
		expectDate = processOrder.ExpectDeliverDate.Time.Format(util.DateLayout)
	}
	if processOrder.PrepareTime.Valid {
		prepareTime = processOrder.PrepareTime.Time.Format(util.DefaultLayout)
	}
	if processOrder.CompleteTime.Valid {
		completeTime = processOrder.CompleteTime.Time.Format(util.DefaultLayout)
	}

	resp = &inventory.GetOrderInfoResponse{
		OrderNumber:         processOrder.OrderNumber,
		ExtOrderNumber:      processOrder.ExtOrderNumber,
		Status:              inventory.ProcessOrderStatus(processOrder.Status),
		DeliverStatus:       deliveryStatus,
		SubmitTime:          processOrder.SubmitTime.Format(util.DefaultLayout),
		WarehouseName:       warehouse.Name,
		DeliverTime:         deliverTime,
		Source:              inventory.SourceEnum(processOrder.Source),
		Contact:             contact,
		ExpectDeliverDate:   expectDate,
		ProdTrans:           uint64(processOrder.ProdTrans),
		Address:             address.Address,
		Remark:              processOrder.Remark,
		RefundFlag:          uint32(processOrder.RefundFlag),
		NotifyFlag:          processOrder.NotifyFlag,
		DeliverType:         inventory.DeliverType(processOrder.DeliverType),
		ExpectDeliverFee:    uint64(processOrder.ExpectDeliverFee),
		PlatformName:        platform.Name,
		PlatformOrderNumber: expressOrder.TradeNo,
		Details:             views,
		ExtractKey:          processOrder.ExtractKey.String,
		PrepareTime:         prepareTime,
		CompleteTime:        completeTime,
	}
	return
}
